package com.zhuhjay.common.netty.handler;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.corundumstudio.socketio.namespace.Namespace;
import com.zhuhjay.common.base.annotation.ApiLimiter;
import com.zhuhjay.common.base.exception.ApiLimiterException;
import com.zhuhjay.common.base.model.vo.HttpResult;
import com.zhuhjay.common.base.util.ValidateUtils;
import com.zhuhjay.common.netty.interceptor.ListenerInterceptor;
import com.zhuhjay.common.netty.util.NettySocketUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * <p>
 * 原先是想直接使用@Aspect对@OnEvent进行拦截，但是发现拦截不到，所以只能自己实现一个拦截器 {@link ListenerInterceptor}
 * 想法：
 * <p>1. 需要在每一次事件请求之前获取请求用户的信息, 放置到线程上下文方便使用</p>
 * <p>2. 业务逻辑在模块imchat中实现, 解析token在framework中实现, 单单使用DataListener<T>可以发现很难将两者联系起来,
 * 需要再抽象出一个接口, 用来拦截事件, 从而实现业务逻辑和解析token的解耦
 * </p>
 * </p>
 *
 * <p>如果单纯使用注解 @OnConnect 和 @OnDisconnect 是没有办法与该类的成员进行共享的, 所以改成了接口实现并注册的方式</p>
 * <p>每一个事件可以根据需求来注册一些事件(OnConnect, OnEvent, OnDisconnect...) {@link Namespace}</p>
 *
 * @author ZhuHJay
 * @date 2023/7/10 17:38
 */
@Slf4j
public abstract class AbstractSocketHandler<T> implements DataListener<T>, ConnectListener, DisconnectListener {

    /**
     * 保存用户uid和sessionId的映射关系
     */
    public static final ConcurrentMap<String, UUID> UID_SESSION_MAPPING = new ConcurrentHashMap<>();
    protected final SocketIOServer socketServer;
    /**
     * 用于解析token的处理器
     */
    protected final AuthParserHandler authParserHandler;
    /**
     * 限流处理器
     */
    protected final SocketLimiterHandler socketLimiterHandler;

    protected AbstractSocketHandler(
            SocketIOServer socketServer,
            AuthParserHandler authParserHandler,
            SocketLimiterHandler socketLimiterHandler) {
        this.socketServer = socketServer;
        this.authParserHandler = authParserHandler;
        this.socketLimiterHandler = socketLimiterHandler;
    }

    @Override
    public void onConnect(SocketIOClient client) {
        log.info("client conn, c={}@{}, s={}", this.getClass().getSimpleName(), this.getClass().hashCode(), client.getSessionId());
    }

    @Override
    public final void onData(SocketIOClient client, T data, AckRequest ackSender) throws Exception {
        authParserHandler.handler(client, () -> {
            try {
                // 限流
                socketLimiterHandler.handler(client, getEventName(), this.getClass().getAnnotation(ApiLimiter.class));
                // 参数校验
                List<String> valid = ValidateUtils.valid(data);
                if (!valid.isEmpty()) {
                    log.warn("参数校验失败, msg={}", valid);
                    return;
                }
                // 业务逻辑
                onEvent(client, data, ackSender);
            } catch (ApiLimiterException e) {
                NettySocketUtils.sendEvent2Self(client, getEventName(), HttpResult.fail(e.getMessage()));
            }
        });
    }

    /**
     * 事件处理: 主要的业务逻辑
     */
    protected void onEvent(SocketIOClient client, T msg, AckRequest ackSender) {
        log.info("client event, c={}@{}, s={}, msg={}", this.getClass().getSimpleName(), this.getClass().hashCode(), client.getSessionId(), msg);
    }

    @Override
    public void onDisconnect(SocketIOClient client) {
        log.info("client disConn, c={}@{}, s={}", this.getClass().getSimpleName(), this.getClass().hashCode(), client.getSessionId());
    }

    /** 事件名称 */
    protected abstract String getEventName();

    /** 数据类型 */
    protected abstract Class<T> getDataType();

}
